package marchingCubes;

import java.awt.geom.Line2D;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Vector;

import controlP5.CheckBox;
import controlP5.ControlP5;
import controlP5.RadioButton;

import marchingCubes.IsMCVertex;
import marchingCubes.MCube;
import marchingCubes.MCubeEngine;
import marchingCubes.MCubeEngineLowMemory;
import meshToSolid.ContainerGrid;
import meshToSolid.STLWriter;
import meshToSolid.ContainerGrid.Container;

import processing.core.PApplet;
import processing.core.PGraphics;
import processing.core.PMatrix3D;
import processing.core.PVector;

import simpleGeom.Bounds3D;
import simpleGeom.Box3D;
import simpleGeom.Face3D;
import simpleGeom.HasBounds3D;
import simpleGeom.HasPos3D;
import simpleGeom.Line3D;
import simpleGeom.SimpleTriangle;

import simpleGeom.Node;
import simpleGeom.SimpleObjLoaderStream;
import simpleGeom.Triangle3DUVN;
import simpleGeom.TrianglePreCalc;
import simpleGeom.Vec3D;

public class DualApplet extends PApplet {
	ArrayList<Node> nodes = new ArrayList<Node>();
	ArrayList<SimpleTriangle> faces = new ArrayList<SimpleTriangle>();
	Bounds3D b3d = new Bounds3D();
	Bounds3D realBounds = new Bounds3D();
	Cell[][][] grid;
	ContainerGrid cGrid;
	int cGridRes = 20;
	ArrayList<Line3D> lines;
	float sX;
	float sY;
	float sZ;

	int nYZ;

	float dim = 0.017f;

	float iso = 0;
	String folderPath = "/Users/caad/Desktop/Archive/";

	String pathObjIn = folderPath + "testPiece5b.obj";// form |
														// 11-09-12
														// |
														// 14'28'40
														// |.obj

	String pathDistOut = folderPath + "testDistHigh4.txt";
	Vector<SimpleTriangle> meFaces;
	boolean flagMarching = false;
	boolean flagDistance = false;

	ControlP5 cp5;

	public int minX = 30;// 26
	public int maxX = 1;// 5
	public int minY = 20;// 20
	public int maxY = 65;// 72
	public int minZ = 28;// 28

	String pathObjOut = "";
	Box3D innerVolume;
	Bounds3D meshBounds;
	boolean isPreview = true;
	boolean flagResetPreview;
	boolean flagResetFine;
	boolean flagRefresh;
	float rotY = 0;

	Vector<SimpleTriangle> borderFaces = new Vector<SimpleTriangle>();
	DualCubeEngine dc = new DualCubeEngine();

	boolean[][][] reached;

	float oZ = 0.2f;
	int nOZ = (int) (oZ / dim);
	float oY = 0.2f;
	int nOY = (int) (oY / dim);

	boolean flagSave;
	boolean drawBox;

	int z1 = nOZ;
	int z2 = dc.nZ - nOZ - 1;

	int y1 = nOY;
	int y2 = dc.nY - nOY - 1;
	int secZ = 0;
	boolean doExport = false;

	public void drawShapes(boolean export) {
		if (export) {
			this.beginRaw(DXF, outputPath);

		}
		fill(255);
		stroke(255, 0, 0);
		rectMode(CENTER);
		int iDraw = 0;
		for (int x = 1; x < dc.nX - 1; x++) {
			for (int y = 1; y < dc.nY - 1; y++) {
				// for (int z = 1; z < dc.nZ-1; z++) {
				DualCube dC = dc.cubes[x][y][secZ];
				if (dC != null) {

					int nOut = 0;
					DualEdge[] nbs = dC.edges;

					for (int i = 0; i < nbs.length; i++) {
						DualEdge e = nbs[i];
						if (e != null) {
							float sX = dc.getXFloat(e.c1.x);
							float sY = dc.getYFloat(e.c1.y);
							float sZ = dc.getZFloat(e.c1.z);
							float dX = dc.getXFloat(e.c2.x) - sX;
							float dY = dc.getYFloat(e.c2.y) - sY;
							float dZ = dc.getZFloat(e.c2.z) - sZ;

							pushMatrix();
							iDraw++;
							// println(cX+" "+cY+" "+cZ);

							translate(sX + dX * 0.5f, sY + dY * 0.5f, sZ + dZ
									* 0.5f);
							if (dY > 0)
								rotateX(HALF_PI);
							else if (dX > 0)
								rotateY(HALF_PI);
							if (dZ == 0) {
								rect(0, 0, dc.dimX, dc.dimY);
							}
							popMatrix();
						}
					}

				}

				// }
			}
		}
		if (export) {
			this.endRaw();

		}
		println("iDraw: " + iDraw);
	}

	public void setup() {
		size(1300, 750, P3D);
		//
		b3d = new Bounds3D();
		realBounds = new Bounds3D();
		realBounds.addPoint(new Vec3D(1.5f, 1.5f, 1.5f));
		realBounds.addPoint(new Vec3D(0, 0, 0));
		// loadData(folderPath+"testcube.obj");
		loadData(pathObjIn);
		float cX = realBounds.getCenterX();
		float cY = realBounds.getCenterY();
		float cZ = realBounds.getCenterZ();
		realBounds.reset();
		for (int i = 0; i < nodes.size(); i++) {
			Node n = nodes.get(i);
			n.setX(n.getX() - cX);
			n.setY(n.getY() - cY);
			n.setZ(n.getZ() - cZ);
			realBounds.addPoint(n);
		}
		// resetPreview();

		createGUI();
		dc.init(realBounds.getOffsetBox(dim * 4f), dim);
		for (int i = 0; i < faces.size(); i++) {
			if (i % 100000 == 0)
				println(i);
			SimpleTriangle sT = faces.get(i);
			dc.scanTriangle(sT.getNode(0), sT.getNode(1), sT.getNode(2));
		}
		z1 = nOZ;
		z2 = dc.nZ - nOZ - 1;

		y1 = nOY;
		y2 = dc.nY - nOY - 1;
		// block by border.
		setBorders(dc);
		
		println(realBounds);
		reached = new boolean[dc.nX][dc.nY][dc.nZ];
		reached[0][nOY + 5][nOZ + 5] = true;

		dc.calculateDepth(reached);
		int nreached = 0;
		int nunreached = 0;
		/*
		 * for (int x = 0; x < dc.nX; x++) { for (int y = 0; y < dc.nY; y++) {
		 * for (int z = 0; z < dc.nZ; z++) { DualCube cube = dc.cubes[x][y][z];
		 * if (cube != null) { cube.setInside(reached[x][y][z]); } } } }
		 */
		// meFaces = dc.getMCFaces();
		meFaces = dc.getFaces();
		println("meFaces: " + meFaces.size());
		
		// saveObjPrintwriter(this.selectOutput());
	}

	public void setBorders(DualCubeEngine dc) {
		Vector<DualEdge> dualEdges = new Vector<DualEdge>();

		for (int x = 0; x < dc.nX; x++) {
			for (int y = 0; y < dc.nY; y++) {
				DualEdge dC1 = dc.addEdge(x, y, z1, x, y, z1 + 1);
				dC1.intersection = new Vec3D(dc.getXFloat(x), dc.getYFloat(y),
						dc.getZFloat(nOZ));
				dC1.normal = new Vec3D(0, 0., 1);
				DualEdge dC2 = dc.addEdge(x, y, z2, x, y, z2 - 1);
				dC2.intersection = new Vec3D(dc.getXFloat(x), dc.getYFloat(y),
						dc.getZFloat(z2 - 1));
				dC2.normal = new Vec3D(0, 0, 1);
			}
		}
		float oY = 0.5f;
		int nOY = (int) (oY / dim);

		for (int x = 0; x < dc.nX; x++) {
			for (int z = 0; z < dc.nZ; z++) {
				DualEdge dC1 = dc.addEdge(x, y1, z, x, y1 + 1, z);
				dC1.intersection = new Vec3D(dc.getXFloat(x),
						dc.getYFloat(nOY), dc.getZFloat(z));
				dC1.normal = new Vec3D(0, 1, 0);
				DualEdge dC2 = dc.addEdge(x, y2, z, x, y2 - 1, z);
				dC2.intersection = new Vec3D(dc.getXFloat(x),
						dc.getYFloat(y2 - 1), dc.getZFloat(z));
				dC2.normal = new Vec3D(0, 1, 0);
			}
		}

		// dc.calculateDepth(reached);
		int nreached = 0;
		int nunreached = 0;
		/*
		 * for (int x = 0; x < dc.nX; x++) { for (int y = 0; y < dc.nY; y++) {
		 * for (int z = 0; z < dc.nZ; z++) { DualCube cube = dc.cubes[x][y][z];
		 * if (cube != null) { cube.setInside(reached[x][y][z]); } } } }
		 */
		meFaces = dc.getFaces();
		// saveObjPrintwriter(this.selectOutput());

	}

	public void radioButton(int a) {
		flagRefresh = true;
		rotY = HALF_PI * a * 0.5f;
		// println("a radio Button event: "+a);
	}

	public void checkBox(float[] a) {
		println(a);
		flagRefresh = true;
	}

	CheckBox checkBox;

	public void createGUI() {
		cp5 = new ControlP5(this);
		cp5.addSlider("minX").setPosition(20, 20).setRange(0, 100)
				.setSize(200, 20);
		;
		cp5.addSlider("maxX").setPosition(20, 50).setRange(0, 100)
				.setSize(200, 20);
		;
		cp5.addSlider("minY").setPosition(20, 80).setRange(0, 100)
				.setSize(200, 20);
		;
		cp5.addSlider("maxY").setPosition(20, 110).setRange(0, 100)
				.setSize(200, 20);
		;
		cp5.addSlider("minZ").setPosition(20, 140).setRange(0, 100)
				.setSize(200, 20);
		;
		// cp5.setAutoDraw(false);

		RadioButton r = cp5.addRadioButton("radioButton").setPosition(20, 170)
				.setSize(10, 10).setColorForeground(color(120))
				.setColorActive(color(255)).setColorLabel(color(255))
				.setItemsPerRow(8).setSpacingColumn(20).addItem("50", 1)
				.addItem("100", 2).addItem("150", 3).addItem("200", 4)
				.addItem("250", 5).addItem("300", 6).addItem("350", 7)
				.addItem("400", 8)

		;
		checkBox = cp5.addCheckBox("checkBox").setSize(30, 30)
				.setPosition(20, 200).addItem("drawBox", 0);
	}

	public void saveSTL(String file) {
		STLWriter writer = new STLWriter();
		writer.beginSave(file + ".stl", meFaces.size());
		for (int i = 0; i < meFaces.size(); i++) {
			SimpleTriangle f = meFaces.get(i);
			writer.face(f.getNode(0), f.getNode(1), f.getNode(2));
		}
		writer.endSave();
	}

	public void loadData(String name) {
		SimpleObjLoaderStream stream = new SimpleObjLoaderStream();
		stream.init(name);
		float[] vertex = stream.readVertex();
		while (vertex != null) {
			nodes.add(new Node(vertex[0], vertex[1], vertex[2]));
			vertex = stream.readVertex();
		}
		println("nodes: " + nodes.size());
		stream.close();
		stream.init(name);
		int[] face = stream.readFace();
		while (face != null) {
			SimpleTriangle f = new SimpleTriangle(null, null, null);
			if (face.length == 4) {
				f = new SimpleTriangle(nodes.get(face[0] - 1),
						nodes.get(face[1] - 1), nodes.get(face[2] - 1));
				faces.add(f);
				f = new SimpleTriangle(nodes.get(face[2] - 1),
						nodes.get(face[3] - 1), nodes.get(face[0] - 1));
				faces.add(f);

				for (int i = 0; i < face.length; i++) {
					Node n = nodes.get(face[i] - 1);
					n.setComment(1);
				}
			} else {
				f = new SimpleTriangle(nodes.get(face[0] - 1),
						nodes.get(face[1] - 1), nodes.get(face[2] - 1));
				for (int i = 0; i < face.length; i++) {
					Node n = nodes.get(face[i] - 1);
					n.setComment(1);
				}
				faces.add(f);
			}
			face = stream.readFace();
		}
		ArrayList<Node> newNodes = new ArrayList<Node>();
		for (int i = 0; i < nodes.size(); i++) {
			Node n = nodes.get(i);
			if (n.getComment() == 1) {
				newNodes.add(n);
			}
		}
		nodes = newNodes;
		realBounds = new Bounds3D();
		realBounds.addAll(nodes);
		// realBounds.z2 = -realBounds.z1;

		stream.close();
		stream = null;
	}

	public void saveObjPrintwriter(String file) {
		PrintWriter output = createWriter(file + ".obj");

		int idV = 1;

		for (int i = 0; i < meFaces.size(); i++) {
			SimpleTriangle f = meFaces.get(i);
			for (int j = 0; j < f.getNodesNum(); j++) {
				Vec3D n = (Vec3D) f.getNode(j);
				if (n.getId() == 0) {
					n.setId(idV);
					idV++;
					output.println("v " + n.x + " " + n.y + " " + n.z);
				}
			}
		}

		for (int i = 0; i < meFaces.size(); i++) {
			SimpleTriangle f = meFaces.get(i);
			int[] ids = new int[f.getNodesNum()];
			String string = "f";
			boolean isInside = false;
			for (int j = 0; j < f.getNodesNum(); j++) {
				Vec3D n = (Vec3D) f.getNode(j);

				string += " " + n.getId();
			}

			output.println(string);

		}

		output.flush(); // Writes the remaining data to the file
		output.close();
	}

	public int xK(float x) {
		return Math.round((x - sX) / dim);
	}

	public int yK(float y) {
		return Math.round((y - sY) / dim);
	}

	public int zK(float z) {
		return Math.round((z - sZ) / dim);
	}

	void gui() {
		// noLights();
		stroke(0);
		fill(0);

		/*PGraphics p3d = ((PGraphics) g);
		PMatrix3D currCameraMatrix = new PMatrix3D(p3d.camera);

		camera();
		// rect(0, 0, 200, 800);
		// cp5.draw();
		p3d.camera = currCameraMatrix;*/

	}

	public void setBounds() {
		b3d.x1 = realBounds.x1 + minX / 100f;
		b3d.x2 = realBounds.x2 + maxX / 100f; // 0.05F
		b3d.z1 = realBounds.z1 + minZ / 100f;// 0.5
		b3d.z2 = realBounds.z2 - minZ / 100f;// 0.5f
		b3d.y1 = realBounds.y1 + minY / 100f;// 0.45
		b3d.y2 = realBounds.y2 - maxY / 100f;
	}

	public void drawEdges() {
		for (int x = 1; x < dc.nX - 1; x++) {
			for (int y = 1; y < dc.nY - 1; y++) {
				for (int z = 1; z < dc.nZ - 1; z++) {
					DualCube dC = dc.cubes[x][y][z];
					if (dC != null) {
						int nOut = 0;
						DualCube[] nbs = new DualCube[6];
						nbs[0] = dc.cubes[x + 1][y][z];
						nbs[1] = dc.cubes[x][y + 1][z];
						nbs[2] = dc.cubes[x - 1][y][z];
						nbs[3] = dc.cubes[x][y - 1][z];
						nbs[4] = dc.cubes[x][y][z - 1];
						nbs[5] = dc.cubes[x][y][z + 1];
						int nE = 0;
						for (int i = 0; i < 6; i++) {
							DualCube nb = nbs[i];
							if (nb != null) {
								DualEdge dE = dc.getEdge(dC.x, dC.y, dC.z,
										nb.x, nb.y, nb.z);
								if (dE != null) {
									nE++;
									float cX = dc.getXFloat(x);
									float cY = dc.getYFloat(y);
									float cZ = dc.getZFloat(z);
									float cX2 = dc.getXFloat(nb.x);
									float cY2 = dc.getYFloat(nb.y);
									float cZ2 = dc.getZFloat(nb.z);
									line(cX, cY, cZ, cX2, cY2, cZ2);
								}
							}
						}
					}
				}
			}
		}
	}

	public void drawBoxes() {
		for (int x = 1; x < dc.nX - 1; x++) {
			for (int y = 1; y < dc.nY - 1; y++) {
				for (int z = 1; z < dc.nZ - 1; z++) {
					DualCube dC = dc.cubes[x][y][z];
					if (dC != null) {

						int nOut = 0;
						DualCube[] nbs = new DualCube[6];
						nbs[0] = dc.cubes[x + 1][y][z];
						nbs[1] = dc.cubes[x][y + 1][z];
						nbs[2] = dc.cubes[x - 1][y][z];
						nbs[3] = dc.cubes[x][y - 1][z];
						nbs[4] = dc.cubes[x][y][z - 1];
						nbs[5] = dc.cubes[x][y][z + 1];
						int nE = 0;
						for (int i = 0; i < 6; i++) {
							DualCube nb = nbs[i];
							if (nb != null) {
								DualEdge dE = dc.getEdge(dC.x, dC.y, dC.z,
										nb.x, nb.y, nb.z);
								if (dE != null) {
									nE++;
								}
							}
						}

						if (!dc.cubes[x + 1][y][z].isInside())
							nOut++;
						if (!dc.cubes[x][y + 1][z].isInside())
							nOut++;
						if (!dc.cubes[x - 1][y][z].isInside())
							nOut++;
						if (!dc.cubes[x][y - 1][z].isInside())
							nOut++;
						if (!dc.cubes[x][y][z + 1].isInside())
							nOut++;
						if (!dc.cubes[x][y][z - 1].isInside())
							nOut++;
						// if (isBorder) {
						if (!dC.isInside() && nE > 0) {
							// Vec3D pt=edge.intersection;
							float cX = dc.getXFloat(x);
							float cY = dc.getYFloat(y);
							float cZ = dc.getZFloat(z);
							// noStroke();
							// point(pt.x,pt.y,pt.z);

							pushMatrix();
							translate(cX, cY, cZ);
							box(dim);
							popMatrix();

						}

					}
					if (reached[x][y][z]) {
						// stroke(255, 255, 0);
						// point(dc.getXFloat(x),
						// dc.getYFloat(y),dc.getZFloat(z));
					}

				}
			}
		}
	}
	
	String outputPath;

	public void draw() {

		drawBox = checkBox.getState(0);

		setBounds();
		if (flagSave) {
			this.saveObjPrintwriter(outputPath);
			flagSave = false;
		}

		hint(ENABLE_DEPTH_TEST);
		if (mousePressed) {
			background(0);
		}
		pushMatrix();
		lights();
		noFill();
		stroke(255, 0, 0);
		scale(200, 200, 200);
		pointLight(50, 50, 80, 5, 5, 5);
		pointLight(80, 50, 50, -5, -5, -5);
		pointLight(50, 50, 80, 5, 2, -5);

		realBounds.display3D(g);
		if (flagRefresh) {

			background(0);
			println("draw");

			randomSeed(1);

			// camera(300, 0, 0, 0, 0, 0, 0, -1, 0);

			noStroke();
			fill(255);

			// rotateY(rotY);
			/*
			 * fill(255,0,0); stroke(0, 0, 255); for (int i = 0; i <
			 * faces.size(); i++) { SimpleTriangle f = faces.get(i);
			 * f.display3D(g); }
			 */
			stroke(255, 255, 0);
			noStroke();
			fill(255);

			int nInside = 0;
			int nOutside = 0;
			for (int x = 1; x < dc.nX - 1; x++) {
				for (int y = 1; y < dc.nY - 1; y++) {
					for (int z = 1; z < dc.nZ - 1; z++) {
						DualCube dC = dc.cubes[x][y][z];
						if (dC != null) {
							boolean isBorder = false;
							Vector<DualCube> nbs = new Vector<DualCube>();

							if (x < dc.nX - 1)
								nbs.add(dc.cubes[x + 1][y][z]);
							if (x > 0)
								nbs.add(dc.cubes[x - 1][y][z]);
							if (y < dc.nY - 1)
								nbs.add(dc.cubes[x][y + 1][z]);
							if (y > 0)
								nbs.add(dc.cubes[x][y - 1][z]);
							if (z < dc.nZ - 1)
								nbs.add(dc.cubes[x][y][z + 1]);
							if (z > 0)
								nbs.add(dc.cubes[x][y][z - 1]);
							isBorder = false;
							if (!dC.isInside()) {
								for (int j = 0; j < nbs.size(); j++) {
									DualCube nb = nbs.get(j);
									// &&dc.getEdge(x,y,z,nb.x,nb.y,nb.z)!=null
									if (nb.isInside()) {
										isBorder = true;
										break;
									}
								}
							}
							for (int i = 0; i < dC.edges.length; i++) {
								DualEdge edge = dC.edges[i];
								if (edge != null) {
									// isBorder = true;
								}
							}
							if (isBorder) {
								// Vec3D pt=edge.intersection;
								float cX = dc.getXFloat(x);
								float cY = dc.getYFloat(y);
								float cZ = dc.getZFloat(z);
								// noStroke();
								// point(pt.x,pt.y,pt.z);

								pushMatrix();
								translate(cX, cY, cZ);
								box(dim);
								popMatrix();

							}

							if (!drawBox) {
								for (int i = 0; i < meFaces.size(); i++) {
									SimpleTriangle f = meFaces.get(i);
									f.display3D(g);
								}
							}
							stroke(255, 0, 255);
							// drawEdges();
							noStroke();
							if (drawBox) {

								drawShapes(doExport);
								doExport = false;
							}

							float tol = 0.03f;

							fill(255);

							noFill();
							stroke(0, 0, 255);
							realBounds.display3D(g);
							stroke(255, 0, 0);
							noStroke();
							fill(255, 30);
							b3d.display3D(g);

							fill(255, 0, 0);
							stroke(255, 0, 0);
							strokeWeight(2f);

							flagRefresh = false;
						}
					}
				}
			}
			

		}
		popMatrix();
		hint(DISABLE_DEPTH_TEST);
		gui();
	}

	public void mouseReleased() {
		println("released");
		flagRefresh = true;
	}

	public void keyPressed() {
		if (key == 'd') {
			flagDistance = true;
			flagMarching = true;
		}
		if (key == 'f') {
			// flagMarching = true;
		}
		if (key == 's') {
			flagSave = true;
		}
		if (key == 'r') {
			flagResetPreview = true;
		}
		if (key == 't') {
			flagResetFine = true;
		}
		if (key == 'i') {
			secZ++;
			secZ = secZ % dc.nZ;
		}
		if (key == 'k') {
			secZ--;
			if (secZ < 0)
				secZ = dc.nZ - 1;
		}
		flagRefresh = true;

	}
}
